from numpy import sqrt, arctan2, power, array

__doc__ =   """ The most basic class to deal with structural/molecular data.
                Do not use it directly since some functions depend on methods provided
                by sub-classes.
            """

class Entity(dict):
    """ Container object all entities inherit from it. Inherits from dict.
    """
    hierarchy = ['H', 'S', 'M', 'C', 'R', 'A'] # duplicate in EntityMisc.py

    def __new__(cls, *args, **kwargs):
        # the masked attribute has to be set before the __init__ of an Entity
        # because during __setstate__, __getstate__ sub-entities are iterated
        # by .values(), which relies on the attribute masked. to decide which
        # children should be omitted.
        cls.masked = False
        return dict.__new__(cls, *args, **kwargs)

    def __init__(self, id, name =None, *args):
        """
        """
        # This class has to be subclassed!
        self.id = (id,)                         # ids are non-zero lenght tuples
        self.name = (name or id)                # prefer name over duplicate id
        self.parent = None                      # mandatory parent attribute
        # Dictionary that keeps addictional properties
        self.xtra = {}                          # mandatory xtra dict attrib
        self.modified = True
        dict.__init__(self, *args)              # finish init as dict

    def __repr__(self):
        """
        """
        # mandatory get_level from sub-class
        return "<Entity id=%s, level=%s>" % (self.get_id(), self.get_level())

    def __hash__(self):
        # This hack anables dicts-likes to be keys in dictionaries
        # it is needed for clustering where a dict-graph stores
        # various entities
        return id(self)

    def __sub__(self, entity):
        """ distance between coordinates.
        """
        return sqrt(sum(pow(self.coords - entity.coords, 2)))

    def set_id(self, id_ =None):
        """ Sets the id.
        """
        if (id_ and id_ != self.id) or (not id_ and (self.get_id() !=  self.id)):
            self.id = (id_ or self.get_id())
            self.set_modified(True, True)
            if self.parent:
                self.parent.update_ids()

    def _set_masked(self, masked):
        if masked != self.masked:
            self.masked = masked             # mask or unmask
            self.set_modified(True, False)   # set parents as modified

    def set_masked(self, masked):
        self._set_masked(masked)

    def set_modified(self, up =True, down =False):
        self.modified = True
        if up and self.parent:
            self.parent.set_modified(True, False)

    def set_parent(self, entity):
        """ Set the parent Entity object.
        """
        if self.parent != entity:
            self.parent = entity
            self.set_modified(False, True)

    def del_parent(self):
        """ Detach from the parent.
        """
        self.set_parent(None)
        self.set_modified(False, True)

    def get_modified(self):
        return self.modified

    def get_masked(self):
        return self.masked

    def set_level(self, level):
        """ Set entity level
        """
        self.level = level

    def get_level(self):
        """ Return level in hierarchy.
        """
        return self.level

    def set_name(self, name):
        """ Set the entity name
        """
        self.name = name
        self.set_id()

    def get_name(self):
        """ Returns entity name
        """
        return self.name

    def get_parent(self, level =None):
        """Return the parent Entity object.
        """
        if not level:
            return self.parent
        elif level == self.level:
            return self
        return self.parent.get_parent(level)

    def get_id(self):
        """ Return the id.
        """
        return (self.name,)

    def get_full_id(self):
        """ Returns the full composite id.
        """
        parent = self.get_parent()
        if parent:
            full_id = parent.get_full_id()
        else:
            full_id = () # we create a tuple on the top
        full_id = full_id + self.get_id()   # merg tuples, left
        return full_id

    def move(self, origin):
        self.coords = self.coords - origin

    def set_coords(self, coords):
        self.coords = coords

    def get_coords(self):
        return self.coords

    def set_scoords(self):
        self.scoords = self.get_scoords()

    def get_scoords(self):
        """ return spherical (r, theta, phi) coordinates.
        """
        x, y, z  = self.coords
        x2,y2,z2 = power(self.coords, 2)
        scoords  = array([sqrt(x2 + y2 + z2),\
                   arctan2(sqrt(x2 + y2), z),\
                   arctan2(y, x)])
        return scoords

    def set_mcoords(self):
        self.mcoords = self.get_mcoords()

    def get_mcoords(self):
        """ ...
        """
        x, y, z  = self.coords
        x2, y2, z2 = power(self.coords, 2)
        mcoords  = array([arctan2(sqrt(y2 + z2), x),\
                          arctan2(sqrt(x2 + z2), y),\
                          arctan2(sqrt(x2 + y2), z)
                          ])
        return mcoords

#EOF